《Kotlin 实战》第2章笔记

Kotlin 实战笔记

函数和变量

变量

变量的声明格式:“关键字 变量名称 [:变量类型]”

声明变量有两个关键字:

  • val:不可变引用,使用 val 声明的变量不能在初始化之后再次赋值。对应 Java 中的 final 变量
  • var:可变引用,这种变量的值可以被改变。对应 Java 中的普遍变量(非 final 变量)

默认情况下应该尽可能地使用 val 关键字来声明变量,仅在必要的时候使用 var

1
2
3
4
//声明变量 a,类型为 Int
val a: Int = 1
//编译器会分析初始化表达式的值,并把它的类型作为变量的类型
val b = 2

函数

用关键字 fun 声明一个函数:

1
2
3
fun main(args: Array<String>) {
println("Hello Kotlin!")
}

声明一个有返回值的函数:

1
2
3
4
//函数的返回值类型为 Int
fun sum(a: Int, b: Int): Int {
return a + b
}

上面这个例子的函数体是由单个表达式(a + b)构成的,可以把这个表达式作为完整的函数体,这种叫做表达式函数体

1
fun sum(a: Int, b: Int): Int = a + b

Kotlin 中还有个类型推导的概念,上面的例子还可以简化成这样:

1
2
3
//省略函数的返回值类型,Kotlin 会自动推导出返回值类型为 Int
//a 和 b 的类型不能省略
fun sum(a: Int, b: Int) = a + b

注意:只有表达式函数体的返回类型可以省略

字符串模版

可以在字符串字面值中引用局部变量,只需要在变量名称前面加上字符 $

1
2
3
4
5
fun main(args: Array<String>) {
val name = "Kotlin"
println("Hello $name!")
}
// 输出:Hello Kotlin!

$ 还可以引用更复杂的表达式,只需要把表达式用 ${} 包起来:

1
2
3
fun main(args: Array<String>) {
println("Hello ${args[0]}")
}

类和属性

属性

用关键字 class 声明一个类:

1
2
3
4
//name、age、isAdult 都是 Person 的属性,name 和 age 类似于 Java 中构造函数的两个参数。
class Person(val name: String, var age: Int) {
val isAdult: Boolean = false
}

声明为 val 的属性是只读的,而 var 的属性是可变的(可读写),只读属性会默认生成对应的 getter 访问器(即 getName()),可变属性会默认生成对应的 getter 和 setter 访问器(即 getAge()setAge(age: Int)

根据上面定义的 Person,来看看如何使用:

1
2
3
4
5
6
7
8
9
10
11
fun main(args: Array<String>) {
//创建一个类的对象时不需要使用 new
val person = Person("ljuns", "20")
//直接访问属性,实际调用的是 getter
println(person.name) //打印结果:ljuns
println(person.age) //打印结果:20
//修改属性,实际调用的是 setter
person.age = 18
println(person.age) //打印结果:18
println(person.isAdult) //打印结果:false
}

自定义访问器

上面例子用的都是默认生成的访问器,其实也可以自定义访问器的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person(val name: String, var age: Int) {
//var 声明的属性需要初始化
var address: String = ""
get() {
return "abc"
}

set(value) {
//此处的 field 暂时理解为当前属性本身
field = value + "123"
}
}
/**
* println(person.address) 打印结果:abc
* person.address = "aaa"
* println(person.address) 打印结果:aaa123
*/

表示和处理选择

枚举类

用两个关键字 enum class 声明枚举:

1
2
3
4
//enum 是一个软关键字,必须出现在 class 前面才有特殊意义,否则就是普通的名称
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

Kotlin 的枚举是可以声明属性的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//声明了三个属性:r、g、b
enum class Color(val r: Int, val g: Int, val b: Int) {
//声明了属性后,定义的常量需要指定属性值
RED(255, 0, 0),
ORANGE(255, 165, 0),
YELLOW(255, 255, 0),
GREEN(0, 255, 0),
BLUE(0, 0, 255),
INDIGO(75, 0, 130),
VIOLET(238, 130, 238); //此处用分号(;)分割枚举常量列表和方法定义,如果没有定义方法可以省略分号

fun rgb() = (r * 256 + g) * 256 + b
}
//println(Color.BLUE.rgb()) 打印结果:255

上面这个例子的打印语句:println(Color.BLUE.rgb()) 等同于:

1
2
val color = Color.BLUE
println(color.rgb())

when

when 是一个有返回值的表达式,类似于 Java 的 switch,但是比 switch 功能更加强大,when 允许使用任何对象。

1
2
3
4
5
6
7
8
9
10
fun getMnemonic(color: Color) =
when (color) {
//不需要写 break,一个分支中可以用逗号(,)隔开多个值
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
//else 类似于 Java 中的 default
else -> throw Exception("Dirty color")
}
//println(getMnemonic(Color.BLUE)) 打印结果:cold

when 中也可以不使用参数:

1
2
3
4
5
fun mixOptimized(color: Color) =
when {
color == Color.RED -> "Red"
else -> throw Exception("Dirty color")
}

如果 when 表达式的分支包含了所有的可能性,那么可以省略 else 分支,否则必须要有 else 分支。

智能转换

Kotlin 使用 is 检查来判断一个变量是否是某种类型,通过检查后可以把变量当作该类型来使用,这种行为称为智能转换。此外,as 表示显示转换为特定类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int {
//使用 is 检查来判断 e 是否是 Num 类型
if(e is Num) {
//使用 as 把 e 显示转换为 Num 类型
//此处可以省略,因为经过 is 的检查后可以把 e 当作是 Num 来使用
val n = e as Num
return n.value
}
}

此外,is 也可以用于 when 表达式的分支中:

1
2
3
4
5
6
fun eval(e: Expr): Int =
when(e) {
is Num -> e.value
is Sum -> e.right
else -> throw IllegalArgumentException("Unknown expression")
}

迭代

while

Kotlin 也有 while 循环和 do-while 循环,语法和 Java 中对应的循环没有什么区别。

区间和数列

区间本质上就是两个值之间的间隔,这两个值通常是数字:一个起始值,一个结束值。如果可以迭代区间中的所有值,这样的区间称为数列。

.. 运算符来表示区间,下面是一个 [1, 10] 的区间:

1
2
//区间是闭包的
val oneToTen = 1..10

until 运算符来表示半闭区间,比如 [1, 10):

1
val oneToTen = 1 until 10

in

使用 in 运算符来检查一个值是否在区间中,或者它的逆运算 !in

1
2
3
4
5
6
// .. 运算符也可以创建字符区间
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'

//println(isLetter('q')) 打印结果:true
//println(isNotDigit('x')) 打印结果:true

同样,in!in 也适用于 when 表达式:

1
2
3
4
5
6
7
fun recognize(c: Char) =
when(c) {
in '0'..'9' -> "It`s a digit!"
in 'a'..'z', in 'A'..'Z' -> "It`s a letter!"
else -> "I don`t know..."
}
//println(recognize('8')) 打印结果:It`s a digit!

迭代集合

通过小例子来学习迭代 map:

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main(args: Array<String>) {
//创建 TreeMap
val binaryReps = TreeMap<Char, String>()
//遍历 [A, F]
for(c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary //类似于 Java中 map.put(c, binary)
}
//迭代(遍历)。定义两个属性 letter、binary 表示 map 的 key 和value
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
}

小结

  • fun 关键字用来声明函数
  • valvar 关键字分别用来声明只读变量和可变变量
  • 在变量名称前面加上 $ 前缀或使用 ${} 包裹一个表达式,可以在字符串字面量中使用
  • 使用两个关键字 enum class 声明枚举类,枚举类中如果定义了方法,需要用分号隔离常量列表和方法
  • is 关键字用来判断变量是否是某种类型,检查后可以把变量当作该类型来使用
  • as 关键字用来显示转换变量为某种类型
  • .. 运算符表示闭区间,until 表示半闭区间
  • in 运算符来检查一个值是否在区间中,还可以使用它的逆运算 !in
  • when 表达式允许使用任何对象,关键字 isin 也可以用在 when 表达式中
坚持原创技术分享,您的支持将鼓励我继续创作!